﻿/* /////////////////////////////////////////////////////////////////////////////////////////////////
https://www.patreon.com/PluginIdea

Licensed under CC BY-SA after EarlyAccess ended. (see https://creativecommons.org/licenses/by-sa/4.0/)

///////////////////////////////////////////////////////////////////////////////////////////////// */

using Battlehub.RTCommon;
using SimpleJSON;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace PluginIdea
{
    public class ObjectScale
    {
        public string id;
        public Vector3 localScale;
    }

    public class DraggedObjectTransInfo
    {
        public Vector3 position;
        public Quaternion rotation;
        public Vector3 localScale;
        public Vector3 forward;
        public Vector3 up;
        public Vector3 right;
    }

    public class BlenderLikeOperation : MVRScript
    {
        // Operation status
        class OperateState
        {
            public const int NONE = 0;
            public const int MOVE = 1;
            public const int SCALE = 2;
            public const int ROTATE = 3;
            public const int MOVE_X = 4;
            public const int MOVE_Y = 5;
            public const int MOVE_Z = 6;
            public const int SCALE_X = 7;
            public const int SCALE_Y = 8;
            public const int SCALE_Z = 9;
            public const int ROTATE_X = 10;
            public const int ROTATE_Y = 11;
            public const int ROTATE_Z = 12;
            public const int MOVE_XY = 13;
            public const int MOVE_XZ = 14;
            public const int MOVE_YZ = 15;
            public const int MOVE_VIEW = 17;
        }

        public class Axis
        {
            public const int AXIS_X = 0;
            public const int AXIS_Y = 1;
            public const int AXIS_Z = 2;
            public const int AXIS_VIEW = 3;
        }

        public class Plane
        {
            public const int AXIS_XY = 0;
            public const int AXIS_XZ = 1;
            public const int AXIS_YZ = 2;
        }

        private bool isWorldSpace;

        private JSONStorableBool isSnap;
        private JSONStorableFloat rotateSnap;
        private JSONStorableString targetsScalingSave;
        private JSONStorableBool disableColliderWhenOperation;
        private JSONStorableFloat moveAlignViewSpeed;

        private float basePointRadio;
        private FreeControllerV3 lastSelectController;
        private Transform controllerTransform;
        private Transform scaleTransform;
        private Rigidbody controllerRigd;
        private int currentState;
        private DraggedObjectTransInfo targetObjectBeginTransInfo;
        private Vector3 targetObjectBeginOffset;
        private Camera targetCamera;
        private CameraHook cameraHook;

        private GLDraw glDraw;

        private float operationBeginDistance;
        private float operationBeginAngle;

        private List<GLLine2D> lines2D;
        private List<GLLine3D> lines3D;

        private Dictionary<string, ObjectScale> dicSceneObjectsScaleChanged = new Dictionary<string, ObjectScale>();

        private Vector3 MousePosition
        {
            get
            {
                float hitObjectDistance = Vector3.Project(controllerTransform.position - targetCamera.transform.position, targetCamera.transform.forward).magnitude;
                float mouseX = Mathf.Clamp(Input.mousePosition.x, 20, Screen.currentResolution.width - 20);
                float mouseY = Mathf.Clamp(Input.mousePosition.y, 20, Screen.currentResolution.height - 20);
                return new Vector3(mouseX, mouseY, hitObjectDistance);
            }
        }

        private Vector3 HitObjectScreenPosition
        {
            get
            {
                return targetCamera.WorldToScreenPoint(controllerTransform.position);
            }
        }

        private float HitObjectScreenPosDistanceToMousePosV2
        {
            get
            {
                Vector2 mouseScreenPosition = new Vector2(MousePosition.x, MousePosition.y);
                Vector2 hitObjectScreenPosition = new Vector2(HitObjectScreenPosition.x, HitObjectScreenPosition.y);
                return (mouseScreenPosition - hitObjectScreenPosition).magnitude;
            }
        }

        // from MacGruber
        private bool IsHandlesEnable
        {
            get {
                SuperController sc = SuperController.singleton;
                UserPreferences up = UserPreferences.singleton;
                bool handlesEnabled = sc.MonitorCenterCamera != null && (!up.showTargetsMenuOnly || sc.mainHUD.gameObject.activeSelf);
                return handlesEnabled;
            }
        }

        private Vector3 ViewDirction
        {
            get {
                return (controllerTransform.position - targetCamera.transform.position).normalized;
            }
        }

        public override void RestoreFromJSON(JSONClass jc, bool restorePhysical = true, bool restoreAppearance = true, JSONArray presetAtoms = null, bool setMissingToDefault = true)
        {
            base.RestoreFromJSON(jc, restorePhysical, restoreAppearance, presetAtoms, setMissingToDefault);
            RestoreScaleInfoFromJson();
        }

        public override void Init()
        {
            base.Init();

            if (SuperController.singleton.isOVR || SuperController.singleton.isOpenVR)
            {
                SuperController.LogError("Not available in VR !");
            }

            //if (containingAtom.type != "CoreControl")
            //{
            //    SuperController.LogError("need add on ScenePluginManager!");
            //    return;
            //}

            UIInit();

            targetCamera = SuperController.singleton.MonitorCenterCamera;
            cameraHook = Utils.GetSafeComponent<CameraHook>(targetCamera.gameObject);
            cameraHook.onPostRenderCallback += OnPostRender;

            glDraw = new GLDraw();
            lines2D = new List<GLLine2D>();
            lines3D = new List<GLLine3D>();
        }

        private void UIInit()
        {
            isSnap = Utils.SetupToggle(this, "Is Snap Active", false, false);
            rotateSnap = Utils.SetupSliderInt(this, "Rotate Angle Snap", 5, 0, 90, false);
            moveAlignViewSpeed = Utils.SetupSliderFloat(this, "Move Range Align ViewDirction", 1f, 0f, 3f, false);
            targetsScalingSave = Utils.SetupStorableString(this, "BlenderLikeScaleSave", "");
            disableColliderWhenOperation = Utils.SetupToggle(this, "IsDisableColliderWhenOperation", false, false);
            Utils.SetupInfoText(this, "<size=40><b>BlenderLike Operation 1.1</b></size>\n" +
                "<b>Use VAM just like using Blender</b>\n\n" +
                "<b>Gizmos colors</b>: red-x axis, green-y axis, blue-z axis.\n\n" +
                "<b>IsDisableColliderWhenOperation</b>: turns off collisions when transforming.\n\n" +
                "<b>IsSnapActive:</b> Is use snap logic with rotate.\n\n" +
                "<b>Rotate Angle Snap:</b> Snap unit angle.\n\n" +
                "<b>Move Range Align ViewDirction:</b> Move Range Align ViewDirction.\n\n" +
                "<b>Note</b>: Because VAM has occupied some shortcut keys. " +
                "So the shortcuts used in this plugin are not exactly the same as those used in Blender (for example, the scale shortcut has been changed to E)." +
                " Switching axes (<b>x, y, z</b>) is also changed to numeric keys<b> 1, 2, 3</b>.\n\n" +
                "<b>Shortcut keys : </b>\n\n" +
                "<b>E:</b> Scale, <b>E+1</b>: ScaleX, <b>E+2</b>: ScaleY, <b>E+3</b>: ScaleZ, <b>E+E</b> : Reset Scale.\n\n" +
                "<b>R:</b> Rotate, <b>R+1</b>: RotateX, <b>R+2</b>: RotateY, <b>R+3</b>: RotateZ, <b>R+R</b> : Reset Rotation.\n\n" +
                "<b>G:</b> Move, <b>G+1</b>: MoveX, <b>G+2</b>: MoveY, <b>G+3</b>: MoveZ, <b>G+G</b> : Reset To Vector3.zero.\n\n" +
                "<b>G+V:</b> Move Along the camera view direction.This usually works when you want to pull an object closer or farther.\n\n" +
                "<b>Right mouse button</b>: returns to an unoperated state.\n\n" +
                "<b>Space</b>: apply transformations.\n\n" +
                "<b>Left Shift</b>: switch to the global coordinate system (by default, " +
                "every operation is in the local coordinate system," +
                " and you need to press Left Shift before you can switch to the global coordinate system).", 800, true);
        }

        private void RestoreScaleInfoFromJson()
        {
            try
            {
                {
                    JSONArray scaleDatas = JSONClass.Parse(targetsScalingSave.val).AsArray;
                    if (scaleDatas.Count > 0)
                    {
                        scaleDatas.Childs.ToList().ForEach(o => {
                            string uid = o["id"];
                            Vector3 localScale = new Vector3(o["x"].AsFloat, o["y"].AsFloat, o["z"].AsFloat);
                            var atom = SuperController.singleton.GetAtomByUid(uid);
                            ObjectScale objectScale = new ObjectScale() { id = uid, localScale = localScale };
                            if (atom != null)
                            {
                                atom.containingAtom.reParentObject.Find("object/rescaleObject").localScale = localScale;
                                if (!dicSceneObjectsScaleChanged.ContainsKey(uid))
                                    dicSceneObjectsScaleChanged.Add(uid, objectScale);
                            }
                            else
                            {
                                SuperController.LogError(string.Format("atom {0} not exist on this scene", atom.name));
                            }
                        });
                    }
                }
            }
            catch (Exception e)
            {
                SuperController.LogMessage(e.Message);
                throw;
            }
        }

        private void AppendSelectAtomScaleInfo()
        {
            try
            {
                var currentAtom = SuperController.singleton.GetSelectedAtom();
                if (currentAtom != null)
                {
                    Vector3 localScale = currentAtom.reParentObject.Find("object/rescaleObject").localScale;
                    if (dicSceneObjectsScaleChanged.ContainsKey(currentAtom.uid))
                    {
                        dicSceneObjectsScaleChanged[currentAtom.uid].localScale = localScale;
                    }
                    else
                    {
                        dicSceneObjectsScaleChanged.Add(currentAtom.uid, new ObjectScale() { id = currentAtom.uid, localScale = localScale });
                    }

                    JSONArray array = new JSONArray();
                    dicSceneObjectsScaleChanged.Values.ToList().ForEach(data => {
                        JSONClass temp = new JSONClass();
                        temp["id"] = data.id;
                        temp["x"] = data.localScale.x.ToString();
                        temp["y"] = data.localScale.y.ToString();
                        temp["z"] = data.localScale.z.ToString();

                        array.Add(temp);
                    });

                    targetsScalingSave.val = array.ToString();
                }
            }
            catch (Exception e)
            {
                SuperController.LogError(e.Message);
                throw;
            }

        }

        void Update()
        {
            lastSelectController = SuperController.singleton.GetSelectedController();
            if (lastSelectController == null && currentState > 0)
            {
                ResetTargetObjectToBeginState();
                return;
            }
            
            RuntimeTools.PivotRotation = isWorldSpace ? RuntimePivotRotation.Global : RuntimePivotRotation.Local;

            // move/rotate controller
            controllerTransform = lastSelectController.transform;
            controllerRigd = controllerTransform.parent.GetComponentInChildren<Rigidbody>();

            // scale target
            SetTransformScale setTransformScale = lastSelectController.GetComponentInParent<SetTransformScale>();
            if (setTransformScale == null)
            {
                scaleTransform = controllerTransform.parent.Find("object/rescaleObject").transform;
            }
            else
            {
                scaleTransform = setTransformScale.transform;
            }

            #region CameraView Align
            if (currentState == OperateState.NONE)
            {
                if (Input.GetKeyDown(KeyCode.G))
                {
                    currentState = OperateState.MOVE;

                    // Switch the collision effect on and off as needed
                    DisableTargetCollider();

                    // Record the TransformInfo before the operation
                    BackUpBeginTransInfo();

                    // for align view move's base info
                    operationBeginDistance = HitObjectScreenPosDistanceToMousePosV2;
                    if (operationBeginDistance == 0) operationBeginDistance = 1;

                    // For camera view drag
                    targetObjectBeginOffset = targetObjectBeginTransInfo.position - targetCamera.ScreenToWorldPoint(MousePosition);

                    // Switch to the logic of the global coordinate system display
                    SetToWorldSpaceCheck();
                }

                if (Input.GetKeyDown(KeyCode.R))
                {
                    currentState = OperateState.ROTATE;

                    DisableTargetCollider();

                    BackUpBeginTransInfo();
                    operationBeginAngle = GetVectorAngleRange360((MousePosition - HitObjectScreenPosition).normalized, Vector3.right);

                    SetToWorldSpaceCheck();
                }

                if (Input.GetKeyDown(KeyCode.E) && scaleTransform != null)
                {
                    currentState = OperateState.SCALE;

                    DisableTargetCollider();

                    BackUpBeginTransInfo();
                    operationBeginDistance = HitObjectScreenPosDistanceToMousePosV2;
                    if (operationBeginDistance == 0) operationBeginDistance = 1;

                    SetToWorldSpaceCheck();
                }

                return;
            }
            #endregion

            #region Handles
            if (currentState == OperateState.MOVE || currentState == OperateState.MOVE_X || currentState == OperateState.MOVE_Y || currentState == OperateState.MOVE_Z)
            {
                ShowPositionHandleView();
            }

            if (currentState == OperateState.ROTATE || currentState == OperateState.ROTATE_X || currentState == OperateState.ROTATE_Y || currentState == OperateState.ROTATE_Z)
            {
                ShowRotateHandleView();
            }
            #endregion

            #region Move Align Axis
            if (currentState == OperateState.MOVE)
            {
                Vector3 worldPosition = targetCamera.ScreenToWorldPoint(MousePosition);
                controllerTransform.position = worldPosition + targetObjectBeginOffset;

                ShowPositionHandleView();

                MoveStateChangeCheck();
            }

            if (currentState == OperateState.MOVE_X)
            {
                MoveAlignAxis(Axis.AXIS_X);
                MoveStateChangeCheck();
            }

            if (currentState == OperateState.MOVE_Y)
            {
                MoveAlignAxis(Axis.AXIS_Y);
                MoveStateChangeCheck();
            }

            if (currentState == OperateState.MOVE_Z)
            {
                MoveAlignAxis(Axis.AXIS_Z);
                MoveStateChangeCheck();
            }

            if (currentState == OperateState.MOVE_VIEW)
            {
                MoveAlignAxis(Axis.AXIS_VIEW);
                MoveStateChangeCheck();
            }
            #endregion

            #region Scale Align Axis
            if (currentState == OperateState.SCALE)
            {
                // scale = current distance / initial distance
                float currentOperationDistance = HitObjectScreenPosDistanceToMousePosV2;
                float scaleRadio = currentOperationDistance / operationBeginDistance;
                basePointRadio = 1 / scaleRadio;
                scaleTransform.localScale = targetObjectBeginTransInfo.localScale * scaleRadio;

                ScaleStateChangeCheck();
            }

            if (currentState == OperateState.SCALE_X)
            {
                ScaleAlignAxis(Axis.AXIS_X);
                ScaleStateChangeCheck();
            }

            if (currentState == OperateState.SCALE_Y)
            {
                ScaleAlignAxis(Axis.AXIS_Y);
                ScaleStateChangeCheck();
            }

            if (currentState == OperateState.SCALE_Z)
            {
                ScaleAlignAxis(Axis.AXIS_Z);
                ScaleStateChangeCheck();
            }
            #endregion

            #region Rotate Align Axis
            if (currentState == OperateState.ROTATE)
            {
                // Notice the order of Quaternion multiplication: from right to left
                float currentOperationAngle = GetVectorAngleRange360((MousePosition - HitObjectScreenPosition).normalized, Vector3.right);

                if (isSnap.val)
                {
                    currentOperationAngle = currentOperationAngle - currentOperationAngle % rotateSnap.val;
                }

                Quaternion quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, ViewDirction);
                controllerTransform.rotation = quater * targetObjectBeginTransInfo.rotation;

                RotateStateChangeCheck();
            }

            if (currentState == OperateState.ROTATE_X)
            {
                RotateAlignAxis(Axis.AXIS_X);
                RotateStateChangeCheck();
            }

            if (currentState == OperateState.ROTATE_Y)
            {
                RotateAlignAxis(Axis.AXIS_Y);
                RotateStateChangeCheck();
            }

            if (currentState == OperateState.ROTATE_Z)
            {
                RotateAlignAxis(Axis.AXIS_Z);
                RotateStateChangeCheck();
            }
            #endregion

            #region Others
            if (Input.GetKeyDown(KeyCode.Space))
            {
                if (currentState == OperateState.SCALE || currentState == OperateState.SCALE_X || currentState == OperateState.SCALE_Y || currentState == OperateState.SCALE_Z)
                {
                    AppendSelectAtomScaleInfo();
                }

                currentState = OperateState.NONE;
           
                EnabelTargetCollider();
            }

            if (Input.GetMouseButtonDown(1) && currentState != OperateState.NONE)
            {
                ResetTargetObjectToBeginState();
            }
            #endregion
        }

        private void ShowRotateHandleView()
        {
            FCRotationHandle rotationHandle = SuperController.singleton.selectedControllerRotationHandle;
            if (rotationHandle != null)
            {
                if (rotationHandle.enabled && !IsHandlesEnable)
                    rotationHandle.ForceDeselectAxis();
                rotationHandle.enabled = IsHandlesEnable;
            }
        }

        private void ShowPositionHandleView()
        {
            FCPositionHandle positionHandle = SuperController.singleton.selectedControllerPositionHandle;
            if (positionHandle != null)
            {
                if (positionHandle.enabled && IsHandlesEnable)
                    positionHandle.ForceDeselectAxis();
                positionHandle.enabled = IsHandlesEnable;
            }
        }

        private void SetToWorldSpaceCheck()
        {
            if (Input.GetKey(KeyCode.LeftShift))
            {
                isWorldSpace = true;
            }
        }

        private void EnabelTargetCollider()
        {
            if (disableColliderWhenOperation.val)
            {
                controllerRigd.isKinematic = false;
            }
        }

        private void DisableTargetCollider()
        {
            if (disableColliderWhenOperation.val)
            {
                controllerRigd.isKinematic = true;
            }
        }

        private void OnDestroy()
        {
            cameraHook.onPostRenderCallback = null;
            Destroy(cameraHook);
        }

        private void RotateStateChangeCheck()
        {
            if (Input.GetKeyDown(KeyCode.Alpha1))
            {
                currentState = OperateState.ROTATE_X;
            }
            else if (Input.GetKeyDown(KeyCode.Alpha2))
            {
                currentState = OperateState.ROTATE_Y;
            }
            else if (Input.GetKeyDown(KeyCode.Alpha3))
            {
                currentState = OperateState.ROTATE_Z;
            }
            else if (Input.GetKeyDown(KeyCode.R))
            {
                currentState = OperateState.NONE;

                controllerTransform.rotation = Quaternion.identity;
            }
        }

        private void ScaleStateChangeCheck()
        {
            if (Input.GetKeyDown(KeyCode.Alpha1))
            {
                currentState = OperateState.SCALE_X;
            }
            else if (Input.GetKeyDown(KeyCode.Alpha2))
            {
                currentState = OperateState.SCALE_Y;
            }
            else if (Input.GetKeyDown(KeyCode.Alpha3))
            {
                currentState = OperateState.SCALE_Z;
            }
            else if (Input.GetKeyDown(KeyCode.E))
            {
                currentState = OperateState.NONE;

                scaleTransform.localScale = Vector3.one;
            }
        }

        private void MoveStateChangeCheck()
        {
            if (Input.GetKeyDown(KeyCode.Alpha1))
            {
                currentState = OperateState.MOVE_X;
            }
            else if (Input.GetKeyDown(KeyCode.Alpha2))
            {
                currentState = OperateState.MOVE_Y;
            }
            else if (Input.GetKeyDown(KeyCode.Alpha3))
            {
                currentState = OperateState.MOVE_Z;
            }
            else if (Input.GetKeyDown(KeyCode.V))
            {
                currentState = OperateState.MOVE_VIEW;
            }
            else if (Input.GetKeyDown(KeyCode.G))
            {
                currentState = OperateState.NONE;

                controllerTransform.position = Vector3.zero;
            }
        }

        float last;
        private void MoveAlignAxis(int axis)
        {
            Ray ray = targetCamera.ScreenPointToRay(Input.mousePosition);
            Vector3 intersectPoint = Vector3.zero;

            if (isWorldSpace)
            {
                if (axis == Axis.AXIS_X)
                {
                    intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, Vector3.back, ray);
                    intersectPoint.x = Mathf.Clamp(intersectPoint.x, targetObjectBeginTransInfo.position.x - 20, targetObjectBeginTransInfo.position.x + 20);
                    controllerTransform.position = new Vector3(intersectPoint.x, targetObjectBeginTransInfo.position.y, targetObjectBeginTransInfo.position.z);
                }
                else if (axis == Axis.AXIS_Y)
                {
                    intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, Vector3.back, ray);
                    intersectPoint.y = Mathf.Clamp(intersectPoint.y, targetObjectBeginTransInfo.position.y - 20, targetObjectBeginTransInfo.position.y + 20);
                    controllerTransform.position = new Vector3(targetObjectBeginTransInfo.position.x, intersectPoint.y, targetObjectBeginTransInfo.position.z);
                }
                else if (axis == Axis.AXIS_Z)
                {
                    intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, Vector3.up, ray);
                    intersectPoint.z = Mathf.Clamp(intersectPoint.z, targetObjectBeginTransInfo.position.z - 20, targetObjectBeginTransInfo.position.z + 20);
                    controllerTransform.position = new Vector3(targetObjectBeginTransInfo.position.x, targetObjectBeginTransInfo.position.y, intersectPoint.z);
                }
            }
            else
            {
                if (axis == Axis.AXIS_VIEW)
                {
                    float currentOperationDistance = HitObjectScreenPosDistanceToMousePosV2;
                    float moveRadio = (currentOperationDistance - operationBeginDistance) / operationBeginDistance;
                    Vector3 controllerTargetPosition = targetObjectBeginTransInfo.position - ViewDirction * moveRadio * moveAlignViewSpeed.val;

                    // too close to camera or is back of camera
                    if ((controllerTargetPosition - targetCamera.transform.position).magnitude < 1f ||
                        Vector3.Dot(targetCamera.transform.forward, (controllerTargetPosition - targetCamera.transform.position).normalized) < 0)
                    {
                        moveRadio = last;
                    }

                    last = moveRadio;
                    basePointRadio = 1 / (moveRadio + 1);
                    controllerTransform.position = targetObjectBeginTransInfo.position - ViewDirction * moveRadio * moveAlignViewSpeed.val;
                }
                else
                {
                    // Calculate the intersection of the ray and the constructed plane (a point on the plane and the normal of the plane determine a plane),
                    // and project the intersection to the corresponding axis of the local axis to get the position to which the current object needs to move.
                    Vector3 offsetVec = Vector3.zero;

                    if (axis == Axis.AXIS_X)
                    {
                        intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, targetObjectBeginTransInfo.forward, ray);
                        intersectPoint.x = Mathf.Clamp(intersectPoint.x, targetObjectBeginTransInfo.position.x - 20, targetObjectBeginTransInfo.position.x + 20);
                        Vector3 intersectPointVec = intersectPoint - targetObjectBeginTransInfo.position;
                        offsetVec = VecProject(intersectPointVec, targetObjectBeginTransInfo.right);
                    }
                    else if (axis == Axis.AXIS_Y)
                    {
                        intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, targetObjectBeginTransInfo.forward, ray);
                        intersectPoint.y = Mathf.Clamp(intersectPoint.y, targetObjectBeginTransInfo.position.y - 20, targetObjectBeginTransInfo.position.y + 20);
                        Vector3 intersectPointVec = intersectPoint - targetObjectBeginTransInfo.position;
                        offsetVec = VecProject(intersectPointVec, targetObjectBeginTransInfo.up);
                    }
                    else if (axis == Axis.AXIS_Z)
                    {
                        intersectPoint = GetIntersectPointWithRay(targetObjectBeginTransInfo.position, targetObjectBeginTransInfo.up, ray);
                        intersectPoint.z = Mathf.Clamp(intersectPoint.z, targetObjectBeginTransInfo.position.z - 20, targetObjectBeginTransInfo.position.z + 20);
                        Vector3 intersectPointVec = intersectPoint - targetObjectBeginTransInfo.position;
                        offsetVec = VecProject(intersectPointVec, targetObjectBeginTransInfo.forward);
                    }

                    controllerTransform.position = targetObjectBeginTransInfo.position + offsetVec;
                }
            }
        }

        private void ScaleAlignAxis(int axis)
        {
            float currentOperationDistance = HitObjectScreenPosDistanceToMousePosV2;
            float scaleRadio = currentOperationDistance / operationBeginDistance;

            if (axis == Axis.AXIS_X)
            {
                scaleTransform.localScale = new Vector3(scaleRadio, targetObjectBeginTransInfo.localScale.y, targetObjectBeginTransInfo.localScale.z);
            }
            else if (axis == Axis.AXIS_Y)
            {
                scaleTransform.localScale = new Vector3(targetObjectBeginTransInfo.localScale.x, scaleRadio, targetObjectBeginTransInfo.localScale.z);
            }
            else if (axis == Axis.AXIS_Z)
            {
                scaleTransform.localScale = new Vector3(targetObjectBeginTransInfo.localScale.x, targetObjectBeginTransInfo.localScale.y, scaleRadio);
            }
        }

        private void RotateAlignAxis(int axis)
        {
            float currentOperationAngle = GetVectorAngleRange360((MousePosition - HitObjectScreenPosition).normalized, Vector3.right);

            if (isSnap.val)
            {
                currentOperationAngle = currentOperationAngle - currentOperationAngle % rotateSnap.val;
            }

            Quaternion quater = Quaternion.identity;

            if (isWorldSpace)
            {
                if (axis == Axis.AXIS_X)
                {
                    quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, Vector3.right);
                }
                else if (axis == Axis.AXIS_Y)
                {
                    quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, Vector3.up);
                }
                else if (axis == Axis.AXIS_Z)
                {
                    quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, Vector3.forward);
                }
            }
            else
            {
                if (axis == Axis.AXIS_X)
                {
                    quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, targetObjectBeginTransInfo.right);
                }
                else if (axis == Axis.AXIS_Y)
                {
                    quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, targetObjectBeginTransInfo.up);
                }
                else if (axis == Axis.AXIS_Z)
                {
                    quater = Quaternion.AngleAxis(currentOperationAngle - operationBeginAngle, targetObjectBeginTransInfo.forward);
                }
            }

            controllerTransform.rotation = quater * targetObjectBeginTransInfo.rotation;
        }

        private void BackUpBeginTransInfo()
        {
            targetObjectBeginTransInfo = new DraggedObjectTransInfo()
            {
                position = controllerTransform.position,
                localScale = scaleTransform != null ? scaleTransform.localScale : Vector3.one,
                rotation = controllerTransform.rotation,
                forward = controllerTransform.forward,
                up = controllerTransform.up,
                right = controllerTransform.right,
            };
        }

        private void ResetTargetObjectToBeginState()
        {
            if (controllerTransform != null && targetObjectBeginTransInfo != null)
            {
                controllerTransform.position = targetObjectBeginTransInfo.position;
                controllerTransform.rotation = targetObjectBeginTransInfo.rotation;

                if (scaleTransform != null)
                {
                    scaleTransform.localScale = targetObjectBeginTransInfo.localScale;
                }
            }

            currentState = OperateState.NONE;
            isWorldSpace = false;

            EnabelTargetCollider();
        }

        void OnPostRender()
        {
            if (currentState > 0)
            {
                if (Camera.current.name == "ScreenUICamera") return;

                if (lines2D.Count > 0)
                {
                    lines2D.Clear();
                }

                if (lines3D.Count > 0)
                {
                    lines3D.Clear();
                }

                // draw gizmos line
                Vector2 beginPosition = targetCamera.WorldToScreenPoint(targetObjectBeginTransInfo.position);

                if (currentState == OperateState.MOVE || currentState == OperateState.MOVE_X || currentState == OperateState.MOVE_Y || currentState == OperateState.MOVE_Z)
                {
                    Vector2 currentPosition = HitObjectScreenPosition;
                    lines2D.Add(new GLLine2D() { from = MapScreenPosTo01(beginPosition), to = MapScreenPosTo01(currentPosition) });
                    glDraw.DrawLine2D(lines2D, Color.white);
                }
                else if (currentState == OperateState.MOVE_VIEW)
                {
                    Vector2 currentPosition = MousePosition;
                    Vector2 v1 = MapScreenPosTo01(beginPosition);
                    Vector2 v2 = MapScreenPosTo01(currentPosition);
                    Vector2 direction = (v2 - v1).normalized;
                    float length = (v2 - v1).magnitude;
                    Vector2 leftPoint = v2 + 0.1f * length * (direction + GetVerticalVectorFrom(direction));
                    Vector2 rightPoint = v2 + 0.1f * length * (direction - GetVerticalVectorFrom(direction));
                    Vector2 basePointEnd = v1 + direction * length * basePointRadio;
                    Vector2 leftBasePoint = v1 + length * basePointRadio * direction + GetVerticalVectorFrom(direction) * 0.1f * length;
                    Vector2 rightBasePoint = v1 + length * basePointRadio * direction - GetVerticalVectorFrom(direction) * 0.1f * length;

                    lines2D.Add(new GLLine2D() { from = v1, to = v2 });
                    lines2D.Add(new GLLine2D() { from = v2, to = leftPoint });
                    lines2D.Add(new GLLine2D() { from = v2, to = rightPoint });
                    lines2D.Add(new GLLine2D() { from = v1, to = basePointEnd });
                    lines2D.Add(new GLLine2D() { from = leftBasePoint, to = rightBasePoint });

                    glDraw.DrawLine2D(lines2D, Color.white);
                }
                else if (currentState == OperateState.SCALE || currentState == OperateState.SCALE_X || currentState == OperateState.SCALE_Y || currentState == OperateState.SCALE_Z)
                {
                    Vector2 currentPosition = MousePosition;
                    Vector2 v1 = MapScreenPosTo01(beginPosition);
                    Vector2 v2 = MapScreenPosTo01(currentPosition);
                    Vector2 direction = (v2 - v1).normalized;
                    float length = (v2 - v1).magnitude;
                    Vector2 leftPoint = v2 - 0.1f * length * (direction + GetVerticalVectorFrom(direction));
                    Vector2 rightPoint = v2 - 0.1f * length * (direction - GetVerticalVectorFrom(direction));
                    Vector2 basePointEnd = v1 + direction * length * basePointRadio;
                    Vector2 leftBasePoint = v1 + length * basePointRadio * direction + GetVerticalVectorFrom(direction) * 0.1f * length;
                    Vector2 rightBasePoint = v1 + length * basePointRadio * direction - GetVerticalVectorFrom(direction) * 0.1f * length;

                    lines2D.Add(new GLLine2D() { from = v1, to = v2 });
                    lines2D.Add(new GLLine2D() { from = v2, to = leftPoint });
                    lines2D.Add(new GLLine2D() { from = v2, to = rightPoint });
                    lines2D.Add(new GLLine2D() { from = v1, to = basePointEnd });
                    lines2D.Add(new GLLine2D() { from = leftBasePoint, to = rightBasePoint });

                    glDraw.DrawLine2D(lines2D, Color.white);
                }
                else if (currentState == OperateState.ROTATE || currentState == OperateState.ROTATE_X || currentState == OperateState.ROTATE_Y || currentState == OperateState.ROTATE_Z)
                {
                    Vector2 currentPosition = MousePosition;
                    Vector2 v1 = MapScreenPosTo01(beginPosition);
                    Vector2 v2 = MapScreenPosTo01(currentPosition);
                    Vector2 direction = (v2 - v1).normalized;
                    float length = (v2 - v1).magnitude;
                    Vector2 leftPoint = v2 + 0.1f * length * GetVerticalVectorFrom(direction);
                    Vector2 rightPoint = v2 - 0.1f * length * GetVerticalVectorFrom(direction);

                    lines2D.Add(new GLLine2D() { from = v1, to = v2 });
                    lines2D.Add(new GLLine2D() { from = v2, to = leftPoint });
                    lines2D.Add(new GLLine2D() { from = v2, to = rightPoint });

                    glDraw.DrawLine2D(lines2D, Color.white);
                }

                // draw axis line
                if (isWorldSpace)
                {
                    lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - Vector3.right * 100, to = targetObjectBeginTransInfo.position + Vector3.right * 100, color = Color.red });
                    lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - Vector3.forward * 100, to = targetObjectBeginTransInfo.position + Vector3.forward * 100, color = Color.blue });
                    lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - Vector3.up * 100, to = targetObjectBeginTransInfo.position + Vector3.up * 100, color = Color.green });
                }
                else
                {
                    lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - targetObjectBeginTransInfo.right * 100, to = targetObjectBeginTransInfo.position + targetObjectBeginTransInfo.right * 100, color = Color.red });
                    lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - targetObjectBeginTransInfo.forward * 100, to = targetObjectBeginTransInfo.position + targetObjectBeginTransInfo.forward * 100, color = Color.blue });
                    lines3D.Add(new GLLine3D() { from = targetObjectBeginTransInfo.position - targetObjectBeginTransInfo.up * 100, to = targetObjectBeginTransInfo.position + targetObjectBeginTransInfo.up * 100, color = Color.green });
                }

                glDraw.DrawLine3D(lines3D, null, Color.white);
            }
        }

        private Vector2 MapScreenPosTo01(Vector3 screenPosition)
        {
            return new Vector2(screenPosition.x / (float)Screen.currentResolution.width, screenPosition.y / (float)Screen.currentResolution.height);
        }

        private float GetVectorAngleRange360(Vector3 from, Vector3 to)
        {
            // Determine whether the vector of the cross product points off the screen to determine whether the rotation angle is greater than 180.
            Vector3 crossVec = Vector3.Cross(from, to);
            float dotVal = Vector3.Dot(crossVec, Vector3.back);
            float angle = Vector3.Angle(from, to);

            if (dotVal < 0)
            {
                angle = 360 - angle;
            }
            return angle;
        }

        // The projection of src in the direction of target
        public static Vector3 VecProject(Vector3 src, Vector3 target)
        {
            return Vector3.Dot(src, target) * target.normalized;
        }

        // Point of intersection of X-ray and plane
        private Vector3 GetIntersectPointWithRay(Vector3 planePoint, Vector3 n, Ray ray)
        {
            float d = Vector3.Dot(planePoint, n);
            Vector3 rayStartPoint = ray.origin;
            Vector3 rayDirction = ray.direction.normalized;
            float t0 = (d - Vector3.Dot(rayStartPoint, n)) / Vector3.Dot(rayDirction, n);
            return rayStartPoint + rayDirction * t0;
        }

        // Get the vertical vector of the two-dimensional vector (point multiplied by 0, there are two)
        private Vector2 GetVerticalVectorFrom(Vector2 from)
        {
            return new Vector2(-from.y, from.x).normalized;
        }
    }

}

